using System;
using System.Xml;
using System.Collections.Generic;
using System.IO;

namespace SanFS.DiscLibrary
{	
	public class DiscImage : IDisposable
	{
		private bool						isDisposed = false;
		private bool						mOpened = false;
		private FileInfo					mImageFileInfo;
		private IDisc						mDisc;
		private string						mMountDir;	// Path to somewhere we can mount the image
		private long						mEstimatedSpaceFree;
		
		public DiscImage(string ImageFile, string MountDir)
		{	
			mMountDir = MountDir;
			mImageFileInfo = new FileInfo(ImageFile);
			
			mDisc = new Disc();
			mDisc.Uuid = Guid.NewGuid();
			mDisc.Version = 1;
			mDisc.Type = EDiscType.Unknown;
			mDisc.Status = EDiscStatus.Pending;
		}
		
		~DiscImage()
		{
			Dispose(false);
		}
		
		protected void Dispose(bool Disposing)
		{
			if (Disposing)
			{
				// Dispose managed resources
			}
			
			// Dispose unmanaged resources
			UnmountImage();
			
			isDisposed = true;
		}
		
		public void Dispose()
		{
			Dispose(true);
			GC.SuppressFinalize(this);
		}
		
		/*
		 * Method  -> Open()
		 * Desc    -> Open the DiscImage for use.
		 * Params  -> None
		 * Returns -> None
		 */
		public void Open()
		{
			CreateFilesystem();
			MountImage();
			Directory.Delete(mMountDir + "/lost+found");
			mEstimatedSpaceFree = this.GetFilesystemAvailableSpace();
			mOpened = true;
		}
		
		public void Close()
		{
			/*
			 * Create the .discinfo.xml file
			 */
			 mDisc.WriteMetaInfo(mMountDir + "/.discinfo.xml");
			 UnmountImage();
			 mOpened = false;
		}		
		
		public void AddFiles(string Path)
		{
			if (!mOpened)
				throw new DiscManagerException("Image not open");
			RecurseAddFiles(new DirectoryInfo(Path), Path);
		}
		
		public void AddFile(FileInfo Info, string DiscPath)
		{
			if (!mOpened)
				throw new DiscManagerException("Image not open");
			
			Console.WriteLine("AddFile(): Name: {0}, Size: {1}, Space Avail: {2}", Info.Name, Info.Length, mEstimatedSpaceFree);
				
			if (Info.Length >= mEstimatedSpaceFree)
				throw new DiscFullException();
			 	
			CreatePathTargetDirectory(DiscPath);
			try
			{
#if true
				File.Copy(Info.FullName, mMountDir + "/" + DiscPath);
#else
				CmdWrapper	cmd = new CmdWrapper(String.Format("cp -axp \"{0}\" \"{1}/{2}\"", Info.FullName, mMountDir, DiscPath));
				cmd.MergeStderr = true;
				cmd.Run();
#endif
				mDisc.Files.Add((IDiscFile) new DiscFile(DiscPath, Info));
				mEstimatedSpaceFree -= Info.Length;
			}
			catch
			{
				File.Delete(mMountDir + "/" + DiscPath);
				throw;
			}
		}
		
		public void AddFile(string FullPath, string DiscPath)
		{
			FileInfo	fi = new FileInfo(FullPath);
			AddFile(fi, DiscPath);
		}

		private void MountImage()
		{
			CmdWrapper	cmd;
			string		cmdStr = String.Format("/bin/mount -o loop {0} {1}", mImageFileInfo.FullName, mMountDir);
			cmd = new CmdWrapper(cmdStr);
			cmd.Run();
		}
		
		private void UnmountImage()
		{
			CmdWrapper	cmd = new CmdWrapper(String.Format("/bin/umount {0}", mMountDir));
			cmd.Run();
		}
		
		private void CreateFilesystem()
		{
			String	cmdStr = String.Format("/sbin/mke2fs -F -T largefile4 {0}", mImageFileInfo.FullName);
			CmdWrapper	cmd = new CmdWrapper(cmdStr);
			cmd.Run();
		}
		
		/*
		 * Method  -> CreatePathTargetDirectory()
		 * Desc    -> Creates leading directories for a path
		 * Params  -> Disc Relative Path (ie: 'dir1/dir2/file1')
		 * Returns -> None
		 */
		private void CreatePathTargetDirectory(string DiscPath)
		{
			if (Path.GetDirectoryName(DiscPath) == "")
				return;
			Directory.CreateDirectory(mMountDir + "/" + Path.GetDirectoryName(DiscPath));
		}
		
		private void RecurseAddFiles(DirectoryInfo Dir, string PathPrefix)
		{
			DirectoryInfo[] subDirs = Dir.GetDirectories();
			
			if (!PathPrefix.EndsWith("/"))
				PathPrefix += "/";
			if (PathPrefix.StartsWith("./"))
				PathPrefix = "";
				
			foreach (DirectoryInfo d in subDirs)
				RecurseAddFiles(d, PathPrefix + d.Name);
				
			FileInfo[]	files = Dir.GetFiles();
			foreach (FileInfo f in files)
			{
				if (f.Name == ".discinfo.xml")
				{
					Console.WriteLine("DiscImager.RecurseAddFiles(): Warning - skipping '.discinfo.xml'");
					continue;
				}
				AddFile(f, PathPrefix + f.Name);
			}
		}
		
		private long GetFilesystemAvailableSpace()
		{
			CmdWrapper	cmd = new CmdWrapper("df -B1 -l");
			
			cmd.MergeStderr = true;
			string		[]output = cmd.Run();
			
			for (int i = 0; i < output.Length; i++)
			{
				if (output[i].StartsWith(this.mImageFileInfo.FullName))
				{
					string	[]tok = output[i].Split(' ');
					
					if (tok.Length == 1)
					{
						// String is on next line
						string	parse = output[i+1].Trim();
						string	parse2 = RemoveDuplicateSpaces(parse);
						
						string	[]tok2 = parse2.Split(null);
						return Convert.ToInt64(tok2[2]);
					}
					break;
				}
			}

			return 0;
		}
		
		private string RemoveDuplicateSpaces(string Str)
		{
			string	data = "";
			bool	prevWasSpace = false;
			
			foreach (char c in Str)
			{
				if (char.IsWhiteSpace(c) && prevWasSpace)
					continue;	// skip
				else if (char.IsWhiteSpace(c))
					prevWasSpace = true;
				else
					prevWasSpace = false;
					
				data += c;
			}
			return data;
		}
		
		public long SpaceAvailable
		{
			get
			{
				return GetFilesystemAvailableSpace();
			}
		}
		
	}  // END CLASS
}  // END NAMESPACE
